package cz.proks.easyhelpdesk.gae.core.servlet;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;

public class ProxyServlet extends HttpServlet {
    private final static long serialVersionUID = 8392340437746711107L;    

    private final static Set<String> DONT_PROXY_HEADERS = new HashSet<String>();

    static {
        DONT_PROXY_HEADERS.add("proxy-connection");
        DONT_PROXY_HEADERS.add("connection");
        DONT_PROXY_HEADERS.add("keep-alive");
        DONT_PROXY_HEADERS.add("transfer-encoding");
        DONT_PROXY_HEADERS.add("te");
        DONT_PROXY_HEADERS.add("trailer");
        DONT_PROXY_HEADERS.add("proxy-authorization");
        DONT_PROXY_HEADERS.add("proxy-authenticate");
        DONT_PROXY_HEADERS.add("upgrade");

        // APPENGINE CONSTRAINTS
        DONT_PROXY_HEADERS.add("content-length");
        DONT_PROXY_HEADERS.add("host");
        DONT_PROXY_HEADERS.add("vary");
        DONT_PROXY_HEADERS.add("via");
        DONT_PROXY_HEADERS.add("host");
        DONT_PROXY_HEADERS.add("x-forwarded-for");
        DONT_PROXY_HEADERS.add("x-proxyuser-ip");                
    }

    private final static String INIT_PROXY_TO_PARAM = "proxyTo";
    private final static String INIT_PREFIX_PARAM = "prefix";

    private final Logger log = LoggerFactory.getLogger(getClass());

    private String proxyTo;
    private String prefix;

    @Override
    public void init(ServletConfig config) throws ServletException {
        proxyTo = config.getInitParameter(INIT_PROXY_TO_PARAM);
        prefix = config.getInitParameter(INIT_PREFIX_PARAM);

        if (proxyTo == null || prefix == null || proxyTo.isEmpty() || prefix.isEmpty())
            throw new IllegalStateException("Init params " + INIT_PROXY_TO_PARAM + " & " + INIT_PREFIX_PARAM + " must be set!");

        super.init(config);
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        log.debug("service():: Start");

        URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();

        String proxyUrl = getProxyUrl(request.getRequestURI(), request.getQueryString());

        log.debug("service():: Proxy url is {}", proxyUrl);

        try {
            URL url = new URL(proxyUrl);
            
            
            FetchOptions opts = FetchOptions.Builder
                                    .allowTruncate()
                                    .followRedirects()
                                    .doNotValidateCertificate();
            
            HTTPRequest fetchRequest = new HTTPRequest(url, HTTPMethod.valueOf(request.getMethod()), opts);

            copyRequest(request, fetchRequest);

            HTTPResponse fetchResponse = fetcher.fetch(fetchRequest);

            copyResponse(fetchResponse, response);

            if (!response.isCommitted())
                response.sendRedirect(request.getRemoteHost());

            log.debug("service():: Exit");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String getProxyUrl(String requestUri, String requestQuery) {
        if (!requestUri.startsWith(prefix))
            throw new IllegalArgumentException("Request URI doesn't start with prefix! RequestURI: " + requestUri + ", Prefix: " + prefix);

        StringBuilder uriBuilder = new StringBuilder();
        uriBuilder.append(requestUri.substring(prefix.length()));

        if (requestQuery != null && !requestQuery.isEmpty())
            uriBuilder.append("?").append(requestQuery);

        StringBuilder builder = new StringBuilder();
        builder.append(proxyTo).append(uriBuilder);

        return builder.toString();
    }

    private void copyRequest(HttpServletRequest httpServletRequest, HTTPRequest fetcherRequest) throws IOException {

        Enumeration<?> headerNames = httpServletRequest.getHeaderNames();

        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement().toString();

            if (DONT_PROXY_HEADERS.contains(headerName.toLowerCase()))
                continue;
            
            Enumeration<?> values = httpServletRequest.getHeaders(headerName);
            HTTPHeader header = null;
            
            if (values.hasMoreElements()) 
                while(values.hasMoreElements()) {
                    String value = (String) values.nextElement();
                    header = new HTTPHeader(headerName, value);
                    fetcherRequest.addHeader(header);
                }                            
            else {
                header = new HTTPHeader(headerName, null);
                fetcherRequest.addHeader(header);
            }                                    
        }

        byte[] buffer = new byte[2048];
        int read = -1;

        BufferedInputStream bin = new BufferedInputStream(httpServletRequest.getInputStream());
        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        while ((read = bin.read(buffer)) != -1) {
            bout.write(buffer, 0, read);
        }

        bin.close();
        bout.close();

        fetcherRequest.setPayload(bout.toByteArray());
    }

    private void copyResponse(HTTPResponse fetcherResponse, HttpServletResponse httpServletResponse) throws IOException {
        httpServletResponse.setStatus(fetcherResponse.getResponseCode());

        for (HTTPHeader header : fetcherResponse.getHeaders()) {
            httpServletResponse.setHeader(header.getName(), header.getValue());
        }

        BufferedOutputStream out = new BufferedOutputStream(httpServletResponse.getOutputStream());
        out.write(fetcherResponse.getContent());
        out.close();
    }

}